package com.my.study.http;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import com.my.study.json.JSONUtils;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpStatus;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.MessageConstraints;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

/**
 * apache client工具类
 *
 * https://blog.csdn.net/Kincym/article/details/81318492
 * https://zhuanlan.zhihu.com/p/85524697
 *
 * @author: yidujun
 * @create: 2021/08/10 10:15
 */
public class HttpClientUtils {

    private final static int SOCKET_TIMEOUT = 30000;

    private final static int CONNECT_TIMEOUT = 30000;

    private static PoolingHttpClientConnectionManager clientConnectionManager = null;
    private static volatile CloseableHttpClient httpClient = null;
    private static RequestConfig config = null;
    private static HttpRequestRetryHandler httpRequestRetryHandler = null;

    public static CloseableHttpClient getHttpClient() {
        if (null == httpClient) {
            synchronized (HttpClientUtils.class) {
                if (null == httpClient) {
                    init();
                    httpClient = HttpClients.custom()
                            // 设置连接器（池）
                            .setConnectionManager(clientConnectionManager)
                            // 设置默认请求配置
                            .setDefaultRequestConfig(config)
                            // 设置重试策略
                            .setRetryHandler(httpRequestRetryHandler)
                            .build();
                }
            }
        }

        return httpClient;
    }

    private static void init() {
        SSLContext sslContext = null;
        try {
            /* 1、添加对https的支持，需要加载客户端证书*/
            //sslContext = loadClientSSL();
            /* 2、信任所有正式，忽略服务端证书校验*/
            sslContext = loadAllClientSSL();

//            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
//                    sslContext,
//                    new String[]{"TLSv1"},
//                    null,
//                    (s, sslSession) -> true);
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("https", sslsf)
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .build();

            // 配置连接池
            clientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            // 设置最大连接数
            clientConnectionManager.setMaxTotal(50);
            // 设置默认的最大路由连接数
            clientConnectionManager.setDefaultMaxPerRoute(25);
            // 设置到某个路由的最大连接数，会覆盖defaultMaxPerRoute
            clientConnectionManager.setMaxPerRoute(new HttpRoute(new HttpHost("127.0.0.1", 80)), 150);

            /**
             * socket配置（默认配置 和 某个host的配置）
             */
            SocketConfig socketConfig = SocketConfig.custom()
                    //是否立即发送数据，设置为true会关闭Socket缓冲，默认为false
                    .setTcpNoDelay(true)
                    //是否可以在一个进程关闭Socket后，即使它还没有释放端口，其它进程还可以立即重用端口
                    .setSoReuseAddress(true)
                    //接收数据的等待超时时间，单位ms
                    .setSoTimeout(500)
                    //关闭Socket时，要么发送完所有数据，要么等待60s后，就关闭连接，此时socket.close()是阻塞的
                    .setSoLinger(6)
                    //开启监视TCP连接是否有效
                    .setSoKeepAlive(true)
                    .build();
            clientConnectionManager.setDefaultSocketConfig(socketConfig);
            clientConnectionManager.setSocketConfig(new HttpHost("somehost", 80), socketConfig);

            /**
             * HTTP connection相关配置（默认配置 和 某个host的配置）
             * 一般不修改HTTP connection相关配置，故不设置
             */
            //消息约束
            MessageConstraints messageConstraints = MessageConstraints.custom()
                    .setMaxHeaderCount(200)
                    .setMaxLineLength(2000)
                    .build();
            //Http connection相关配置
            ConnectionConfig connectionConfig = ConnectionConfig.custom()
                    .setMalformedInputAction(CodingErrorAction.IGNORE)
                    .setUnmappableInputAction(CodingErrorAction.IGNORE)
                    .setCharset(Consts.UTF_8)
                    .setMessageConstraints(messageConstraints)
                    .build();
            //一般不修改HTTP connection相关配置，故不设置
            //connManager.setDefaultConnectionConfig(connectionConfig);
            //connManager.setConnectionConfig(new HttpHost("somehost", 80), ConnectionConfig.DEFAULT);

            // 配置请求的超时设置
            config = RequestConfig.custom()
                    //连接超时时间
                    .setConnectTimeout(2 * 1000)
                    //读超时时间（等待数据超时时间）
                    .setSocketTimeout(2 * 1000)
                    //从池中获取连接超时时间
                    .setConnectionRequestTimeout(500)
                    //检查是否为陈旧的连接，默认为true，类似testOnBorrow
                    .setStaleConnectionCheckEnabled(true)
                    .build();

            /**
             * 重试处理
             * 默认是重试3次
             */
            //禁用重试(参数：retryCount、requestSentRetryEnabled)
            HttpRequestRetryHandler requestRetryHandler = new DefaultHttpRequestRetryHandler(0, false);
            //自定义重试策略
            httpRequestRetryHandler = (exception, executionCount, context) -> {
                if (executionCount >= 3) {// 如果已经重试了3次，就放弃
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                    return false;
                }
                if (exception instanceof InterruptedIOException) {// 超时
                    return false;
                }
                if (exception instanceof UnknownHostException) {// 目标服务器不可达
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
                    return false;
                }
                if (exception instanceof SSLException) {// SSL握手异常
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext
                        .adapt(context);
                HttpRequest request = clientContext.getRequest();
                //Retry if the request is considered idempotent
                //如果请求类型不是HttpEntityEnclosingRequest，被认为是幂等的，那么就重试
                //HttpEntityEnclosingRequest指的是有请求体的request，比HttpRequest多一个Entity属性
                //而常用的GET请求是没有请求体的，POST、PUT都是有请求体的
                //Rest一般用GET请求获取数据，故幂等，POST用于新增数据，故不幂等
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            };

        } catch (Exception e) {

        }
    }

    /**
     * 加载客户端证书
     *
     * @return
     * @throws KeyStoreException
     * @throws IOException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     * @throws KeyManagementException
     */
    private static SSLContext loadClientSSL() throws KeyStoreException, IOException,
            CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
        // 证书路径
        String KEYSTORE_FILE = "";
        // 证书密码
        String KEYSTORE_PASSWORD = "";
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream instreamSSL = new FileInputStream(new File(KEYSTORE_FILE));
        keyStore.load(instreamSSL, KEYSTORE_PASSWORD.toCharArray());

        return SSLContexts.custom()
                .loadKeyMaterial(keyStore, KEYSTORE_PASSWORD.toCharArray())
                .loadTrustMaterial(null, new TrustSelfSignedStrategy())
                .build();
    }

    /**
     * 信任所有，忽略对服务端证书的校验
     *
     * @return
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    private static SSLContext loadAllClientSSL() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        return SSLContexts.custom()
                .loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true)
                .build();
    }


    /**
     * Get请求
     *
     * @param uri
     * @param responseCharset
     * @return
     */
    public String getForStrings(String uri, String responseCharset) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        HttpGet get = new HttpGet(uri);

        try {
            httpClient = getHttpClient();
            httpResponse = execute(httpClient, get);
            HttpEntity entity = httpResponse.getEntity();
            if (entity != null) {
                return EntityUtils.toString(entity, responseCharset);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(httpResponse);
        }

        return null;
    }

    public String post(String uri, Map<String, Object> params) {
        return post(uri, "UTF-8", null, paramsTransform(params), "UTF-8");
    }

    public String post(String uri, String requestCharset, Map<String, String> headers, Map<String, String> params, String responseCharset) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        HttpPost post = new HttpPost(uri);
        List<BasicNameValuePair> nvp = new ArrayList<>();

        // 转化请求头
        if (CollUtil.isNotEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                post.setHeader(entry.getKey(), entry.getValue());
            }
        }

        // 转化参数
        if (CollUtil.isNotEmpty(params)) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                nvp.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        try {
            httpClient = getHttpClient();
            post.setEntity(new UrlEncodedFormEntity(nvp, requestCharset));
            httpResponse = execute(httpClient, post);
            HttpEntity entity = httpResponse.getEntity();
            if (entity != null) {
                return EntityUtils.toString(entity, responseCharset);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(httpResponse);
        }

        return null;
    }


    public CloseableHttpResponse execute(CloseableHttpClient client, HttpRequestBase base) {
        // 设置超时时间
        setTimeout(base);

        try {
            CloseableHttpResponse response = client.execute(base);
            int status = response.getStatusLine().getStatusCode();

            if (status == HttpStatus.SC_OK) {
                return response;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    public void setTimeout(HttpRequestBase base) {
        RequestConfig config = RequestConfig.custom().setSocketTimeout(SOCKET_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).build();
        base.setConfig(config);
    }

    public void close(CloseableHttpResponse response) {
        try {
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Map<String, String> paramsTransform(Map<String, Object> inParams) {
        if (CollUtil.isEmpty(inParams)) {
            return new HashMap<>(2);
        }

        Map<String, String> outParams = new HashMap<>(inParams.size());
        for (Map.Entry<String, Object> entry : inParams.entrySet()) {
            String value;
            if (entry.getValue() instanceof Date) {
                value = DateUtil.format((Date) entry.getValue(), "YYYY-MM-dd HH:mm:ss");
            } else {
                value = entry.getValue() == null ? "" : entry.getValue().toString();
            }
            outParams.put(entry.getKey(), value);
        }

        return outParams;
    }

    /**
     * 手机号解密
     *
     * @param content 加密的手机号
     * @param key 解密密钥
     * @return 解密后的手机号
     */
    public static String decryptAES(String content, String key) {
        if (content.length() < 1) {
            return null;
        }
        byte[] byteRresult = new byte[content.length() / 2];
        for (int i = 0; i < content.length() / 2; i++) {
            int high = Integer.parseInt(content.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(content.substring(i * 2 + 1, i * 2 + 2), 16);
            byteRresult[i] = (byte) (high * 16 + low);
        }
        try {
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            random.setSeed(key.getBytes());
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            kgen.init(128, random);
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            System.out.println(" >> " + new String(byteRresult));
            byte[] result = cipher.doFinal(byteRresult);
            return new String(result);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
                | BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }


    public static void main(String[] args) {
        String appId = "cAdRfaRe";
        String appSecret = "843bd446220f7e3c63252b62771bf294bac3bbf0";

        String getTokenUrl = "http://ent-demo-test.yscredit.com/servicePath/getTestToken";
        String getUserInfoUrl = "http://ent-demo-test.yscredit.com/servicePath/qrcode/getUserInfoByToken";

        HttpClientUtils httpClientUtils = new HttpClientUtils();

        StringJoiner joiner = new StringJoiner("&");
        String token = httpClientUtils.getForStrings(getTokenUrl, "UTF-8");
        long requestTime = System.currentTimeMillis();
        joiner.add("appId=" + appId);
        joiner.add("appSecret=" + appSecret);
        joiner.add("requestTime=" + requestTime);
        joiner.add("token=" + token);
        String sign = DigestUtil.md5Hex(joiner.toString());

        Map<String, Object> params = new HashMap<>(8);
        params.put("appId", appId);
        params.put("token", token);
        params.put("requestTime", requestTime);
        params.put("sign", sign);

        String info = httpClientUtils.post(getUserInfoUrl, params);
        JSONObject object = JSONUtils.parseObject(info, JSONObject.class);
        String phone = ((Map) object.get("data")).get("phone").toString();
        String realPhone = decryptAES(phone, DigestUtil.md5Hex(appId));
        System.out.println(realPhone);
    }




}
